In computer science, co-routines are program components that typically generalize subroutines and may allow multiple entry points for suspending and resuming execution. Co-routines are sometimes used to implement cooperative tasks, iterators, infinite lists and pipes. While a subroutine is typically a last-in, first-out construct, the lifespan of a co-routine can be dictated by its use. While a subroutine can return only once; a co-routine can return or yield more than once. While the start of a subroutine is usually its only point of entry, the start of a co-routine is the first point of entry and subsequent points of entry can follow yield commands. For example, like a subroutine, yielding in a co-routine can return the result and control to the calling co-routine but, unlike a subroutine, the next time the co-routine is called, execution can start just after the yield call instead of at the beginning of the co-routine.
Because a co-routine can return multiple times, it is possible to return additional values upon subsequent calls. Co-routines in which subsequent calls yield additional results are often called generators. Traditional subroutines use a single stack that can be pre-allocated at the beginning of program execution. In contrast, because co-routines are able to call on other co-routines as peers, typically additional stacks are allocated for co-routines. Sometimes, stacks are pre-allocated or previously allocated stacks are cached.
Generators also generalize subroutines, and are often used to simplify the writing of iterators. The yield statement in a generator typically does not specify a co-routine to jump to, but instead passes a value back to a parent routine. However, it is still possible to implement a co-routine on top of a generator facility, with the aid of a top-level dispatcher routine that passes control explicitly to child generators identified by tokens passed back from the generators.
Many popular programming languages, including C, C++ and C#, do not have direct support for co-routines within the language, due to the limitations of stack-based subroutine implementation. In situations in which a co-routine would logically be used to implement a mechanism, were it available, typically a subroutine is created that uses a combination of boolean flags and other state variables to maintain internal state between calls. Conditional statements result in the execution of different code paths based on the values of the state variables. Alternatively, an explicit state machine in the form of a switch statement is implemented.
An approach providing an alternative to use of co-routines is use of threads. Threads provide the ability to manage real time cooperative interaction of substantially simultaneously executing pieces of code. Threads are typically preemptively scheduled. Co-routines typically are not. Because threads can be rescheduled at any instant and can execute concurrently, programs using threads have to be careful about locking. In contrast, because co-routines can only be rescheduled at specific points in the program and do not execute concurrently, programs using co-routines can often avoid locking issues entirely.
One approach to implementing co-routines in high level languages abandons portability. Instead, processor-family-specific implementations are written in assembly for functions to save and restore a co-routine context. Care has to be taken in the use and writing of these functions so that variables located on the stack are not overwritten, when co-routines share the same stack. Thus, typically, for stack-based co-routines in high level languages, functions are needed to create and jump between alternate stacks. A third machine-specific function can be provided to create the context for a new co-routine. Traditionally, the stack size for the co-routine is fixed and cannot grow during execution. Often, programs allocate a larger stack than needed to avoid potential stack overflow.